#include "Common/Config.h"
#include "Common/FileFormatCheck.h"
#include "FEXCore/Config/Config.h"
#include "Tools/CommonGUI/IMGui.h"

#include <FEXCore/Utils/Event.h>

#include <map>
#include <memory>
#include <mutex>
#include <filesystem>
#include <fstream>
#include <sys/inotify.h>
#include <thread>
#include <unistd.h>
#include <vector>

namespace {
  static std::chrono::time_point<std::chrono::high_resolution_clock> GlobalTime{};

  static bool ConfigOpen{};
  static bool ConfigChanged{};
  static int EnvironmentVariableSelected{};
  static int HostEnvironmentVariableSelected{};
  static int NamedRootFSSelected{-1};

  static std::string ConfigFilename{};
  static std::unique_ptr<FEXCore::Config::Layer> LoadedConfig{};

  static const char EnvironmentPopupName[] = "#New Environment Variable";
  static const char HostEnvironmentPopupName[] = "#New Host Environment Variable";
  static const char SavedPopupAppName[] = "#SavedApp";
  static const char OpenedPopupAppName[] = "#OpenedApp";

  static bool OpenMsgPopup{};
  static bool SaveMsgIsOpen{};
  static std::string MsgMessage{};
  static const char MsgPopupName[] = "#Msg";
  static std::chrono::high_resolution_clock::time_point MsgTimerStart{};

  static bool SelectedOpenFile{};
  static bool SelectedSaveFileAs{};
  static ImGuiFs::Dialog DialogSaveAs{};
  static ImGuiFs::Dialog DialogOpen{};

  // Named rootfs
  static std::vector<std::string> NamedRootFS{};
  static std::mutex NamedRootFSUpdator{};

  static std::atomic<int> INotifyFD{-1};
  static int INotifyFolderFD{};
  static std::thread INotifyThreadHandle{};
  static std::atomic_bool INotifyShutdown{};

  void OpenMsgMessagePopup(std::string Message) {
    OpenMsgPopup = true;
    MsgMessage = Message;
    MsgTimerStart = std::chrono::high_resolution_clock::now();
    FEX::GUI::HadUpdate();
  }

  void LoadDefaultSettings() {
    ConfigOpen = true;
    ConfigFilename = {};
    LoadedConfig = std::make_unique<FEX::Config::EmptyMapper>();
#define OPT_BASE(type, group, enum, json, default) \
    LoadedConfig->Set(FEXCore::Config::ConfigOption::CONFIG_##enum, std::to_string(default));
#define OPT_STR(group, enum, json, default) \
    LoadedConfig->Set(FEXCore::Config::ConfigOption::CONFIG_##enum, default);
#define OPT_STRARRAY(group, enum, json, default)  // Do nothing
#include <FEXCore/Config/ConfigValues.inl>

    // Erase unnamed options which shouldn't be set
    LoadedConfig->Erase(FEXCore::Config::ConfigOption::CONFIG_IS_INTERPRETER);
    LoadedConfig->Erase(FEXCore::Config::ConfigOption::CONFIG_INTERPRETER_INSTALLED);
    LoadedConfig->Erase(FEXCore::Config::ConfigOption::CONFIG_APP_FILENAME);
    LoadedConfig->Erase(FEXCore::Config::ConfigOption::CONFIG_APP_CONFIG_NAME);
    LoadedConfig->Erase(FEXCore::Config::ConfigOption::CONFIG_IS64BIT_MODE);
  }

  bool OpenFile(std::string Filename,  bool LoadDefault = false) {
    std::error_code ec{};
    if (!std::filesystem::exists(Filename, ec)) {
      if (LoadDefault) {
        LoadDefaultSettings();
        ConfigFilename = Filename;
        OpenMsgMessagePopup("Opened with default options: " + Filename);
        return true;
      }
      OpenMsgMessagePopup("Couldn't open: " + Filename);
      return false;
    }
    ConfigOpen = true;
    ConfigFilename = Filename;
    LoadedConfig = FEXCore::Config::CreateMainLayer(&Filename);
    LoadedConfig->Load();

    // Load default options and only overwrite only if the option didn't exist
#define OPT_BASE(type, group, enum, json, default) \
    if (!LoadedConfig->OptionExists(FEXCore::Config::ConfigOption::CONFIG_##enum)) { LoadedConfig->EraseSet(FEXCore::Config::ConfigOption::CONFIG_##enum, std::to_string(default)); }
#define OPT_STR(group, enum, json, default) \
    if (!LoadedConfig->OptionExists(FEXCore::Config::ConfigOption::CONFIG_##enum)) { LoadedConfig->EraseSet(FEXCore::Config::ConfigOption::CONFIG_##enum, default); }
#define OPT_STRARRAY(group, enum, json, default)  // Do nothing
#include <FEXCore/Config/ConfigValues.inl>

    // Erase unnamed options which shouldn't be set
    LoadedConfig->Erase(FEXCore::Config::ConfigOption::CONFIG_IS_INTERPRETER);
    LoadedConfig->Erase(FEXCore::Config::ConfigOption::CONFIG_INTERPRETER_INSTALLED);
    LoadedConfig->Erase(FEXCore::Config::ConfigOption::CONFIG_APP_FILENAME);
    LoadedConfig->Erase(FEXCore::Config::ConfigOption::CONFIG_APP_CONFIG_NAME);
    LoadedConfig->Erase(FEXCore::Config::ConfigOption::CONFIG_IS64BIT_MODE);

    return true;
  }

  void LoadNamedRootFSFolder() {
    std::scoped_lock<std::mutex> lk{NamedRootFSUpdator};
    NamedRootFS.clear();
    std::string RootFS = FEXCore::Config::GetDataDirectory() + "RootFS/";
    std::error_code ec{};
    if (!std::filesystem::exists(RootFS, ec)) {
      // Doesn't exist, create the the folder as a user convenience
      if (!std::filesystem::create_directories(RootFS, ec)) {
        // Well I guess we failed
        return;
      }
    }
    for (auto &it : std::filesystem::directory_iterator(RootFS)) {
      if (it.is_directory()) {
        NamedRootFS.emplace_back(it.path().filename());
      }
      else if (it.is_regular_file()) {
        // If it is a regular file then we need to check if it is a valid archive
        if (it.path().extension() == ".sqsh" &&
            FEX::FormatCheck::IsSquashFS(it.path().string())) {
          NamedRootFS.emplace_back(it.path().filename());
        }
        else if (it.path().extension() == ".ero" &&
            FEX::FormatCheck::IsEroFS(it.path().string())) {
          NamedRootFS.emplace_back(it.path().filename());
        }
      }
    }
    std::sort(NamedRootFS.begin(), NamedRootFS.end());
  }

  void INotifyThread() {
    while (!INotifyShutdown) {
      constexpr size_t DATA_SIZE = (16 * (sizeof(struct inotify_event) + NAME_MAX + 1));
      char buf[DATA_SIZE];
      int Ret{};
      do {
        fd_set Set{};
        FD_ZERO(&Set);
        FD_SET(INotifyFD, &Set);
        struct timeval tv{};
        // 50 ms
        tv.tv_usec = 50000;
        Ret = select(INotifyFD + 1, &Set, nullptr, nullptr, &tv);
      } while (Ret == 0 && INotifyFD != -1);

      if (Ret == -1 || INotifyFD == -1) {
        // Just return on error
        INotifyShutdown = true;
        return;
      }

      // Spin through the events, we don't actually care what they are
      while (read(INotifyFD, buf, DATA_SIZE) > 0);

      // Now update the named vector
      LoadNamedRootFSFolder();

      FEX::GUI::HadUpdate();
    }
  }

  void SetupINotify() {
    if (INotifyFD != -1) {
      // Already setup
      return;
    }

    INotifyFD = inotify_init1(IN_NONBLOCK | IN_CLOEXEC);
    INotifyShutdown = false;

    std::string RootFS = FEXCore::Config::GetDataDirectory() + "RootFS/";
    INotifyFolderFD = inotify_add_watch(INotifyFD, RootFS.c_str(), IN_CREATE | IN_DELETE);
    if (INotifyFolderFD != -1) {
      INotifyThreadHandle = std::thread(INotifyThread);
    }
  }

  void ShutdownINotify() {
    close(INotifyFD);
    INotifyFD = -1;
    if (INotifyThreadHandle.joinable()) {
      INotifyThreadHandle.join();
    }
  }

  void SaveFile(std::string Filename) {
    if (SaveMsgIsOpen) {
      // Don't try saving a file while the message is already open.
      // Stops us from spam saving the file to the filesystem.
      return;
    }

    if (!ConfigOpen) {
      OpenMsgMessagePopup("Can't save file when config isn't open");
      return;
    }

    FEX::Config::SaveLayerToJSON(Filename, LoadedConfig.get());
    ConfigChanged = false;
    ConfigFilename = Filename;
    OpenMsgMessagePopup("Config Saved to: '" + Filename + "'");
    SaveMsgIsOpen = true;

    // Output in terminal as well
    printf("Config Saved to: '%s'\n", ConfigFilename.c_str());
  }

  void CloseConfig() {
    ConfigOpen = false;
    ConfigFilename = {};
    ConfigChanged = false;
    LoadedConfig.reset();
  }

  void FillCPUConfig() {
    char BlockSize[32]{};
    char EmulatedCPUCores[32]{};

    if (ImGui::BeginTabItem("CPU")) {
      std::optional<std::string*> Value{};
#ifdef INTERPRETER_ENABLED
      ImGui::Text("Core:");
      Value = LoadedConfig->Get(FEXCore::Config::ConfigOption::CONFIG_CORE);

      ImGui::SameLine();
      if (ImGui::RadioButton("Int", Value.has_value() && **Value == "0")) {
        LoadedConfig->EraseSet(FEXCore::Config::ConfigOption::CONFIG_CORE, "0");
        ConfigChanged = true;
      }
      ImGui::SameLine();
      if (ImGui::RadioButton("JIT", Value.has_value() && **Value == "1")) {
        LoadedConfig->EraseSet(FEXCore::Config::ConfigOption::CONFIG_CORE, "1");
        ConfigChanged = true;
      }
#endif
      Value = LoadedConfig->Get(FEXCore::Config::ConfigOption::CONFIG_MAXINST);
      if (Value.has_value() && !(*Value)->empty()) {
        strncpy(BlockSize, &(*Value)->at(0), 32);
      }
      if (ImGui::InputText("Block Size:", BlockSize, 32, ImGuiInputTextFlags_EnterReturnsTrue)) {
        LoadedConfig->EraseSet(FEXCore::Config::ConfigOption::CONFIG_MAXINST, BlockSize);
        ConfigChanged = true;
      }

      Value = LoadedConfig->Get(FEXCore::Config::ConfigOption::CONFIG_MULTIBLOCK);
      bool Multiblock = Value.has_value() && **Value == "1";
      if (ImGui::Checkbox("Multiblock", &Multiblock)) {
        LoadedConfig->EraseSet(FEXCore::Config::ConfigOption::CONFIG_MULTIBLOCK, Multiblock ? "1" : "0");
        ConfigChanged = true;
      }

      Value = LoadedConfig->Get(FEXCore::Config::ConfigOption::CONFIG_THREADS);
      if (Value.has_value() && !(*Value)->empty()) {
        strncpy(EmulatedCPUCores, &(*Value)->at(0), 32);
      }
      if (ImGui::InputText("Emulated CPU cores:", EmulatedCPUCores, 32, ImGuiInputTextFlags_EnterReturnsTrue)) {
        LoadedConfig->EraseSet(FEXCore::Config::ConfigOption::CONFIG_THREADS, EmulatedCPUCores);
        ConfigChanged = true;
      }

      ImGui::EndTabItem();
    }
  }

  template<FEXCore::Config::ConfigOption Option>
  bool EnvironmentVariableFiller(void *data, int idx, const char** out_text) {
    static char TmpString[256];
    auto Value = LoadedConfig->All(Option);
    if (Value.has_value()) {
      auto List = (*Value);
      auto it = List->begin();

      // Since this is a list, we don't have a linear allocator that we can just jump to an element
      // Just do a quick spin
      for (int i = 0; i < idx; ++i)
        ++it;

      snprintf(TmpString, 256, "%s", it->c_str());
      *out_text = TmpString;

      return true;
    }

    return false;
  }

  bool NamedRootFSVariableFiller(void *data, int idx, const char** out_text) {
    std::scoped_lock<std::mutex> lk{NamedRootFSUpdator};
    static char TmpString[256];
    if (idx >= 0 && idx < NamedRootFS.size()) {
      // Since this is a list, we don't have a linear allocator that we can just jump to an element
      // Just do a quick spin
      snprintf(TmpString, 256, "%s", NamedRootFS.at(idx).c_str());
      *out_text = TmpString;

      return true;
    }

    return false;
  }


  template<FEXCore::Config::ConfigOption Option>
  void DeleteEnvironmentVariable(int idx) {
    auto Value = LoadedConfig->All(Option);
    auto List = (*Value);
    auto it = List->begin();

    // Since this is a list, we don't have a linear allocator that we can just jump to an element
    // Just do a quick spin
    for (int i = 0; i < idx; ++i)
      ++it;

    List->erase(it);
    ConfigChanged = true;
  }

  void AddNewEnvironmentVariable() {
    char Environment[256]{};
    char HostEnvironment[256]{};

    if (ImGui::BeginPopup(EnvironmentPopupName)) {
      if (ImGui::InputText("New Environment", Environment, 256, ImGuiInputTextFlags_EnterReturnsTrue)) {
        LoadedConfig->Set(FEXCore::Config::ConfigOption::CONFIG_ENV, Environment);
        ImGui::CloseCurrentPopup();
        ConfigChanged = true;
      }

      ImGui::EndPopup();
    }

    ImGui::PushID(1);
    if (ImGui::BeginPopup(HostEnvironmentPopupName)) {
      if (ImGui::InputText("New Environment", HostEnvironment, 256, ImGuiInputTextFlags_EnterReturnsTrue)) {
        LoadedConfig->Set(FEXCore::Config::ConfigOption::CONFIG_HOSTENV, HostEnvironment);
        ImGui::CloseCurrentPopup();
        ConfigChanged = true;
      }

      ImGui::EndPopup();
    }
    ImGui::PopID();
  }

  void FillEmulationConfig() {
    char RootFS[256]{};
    char ThunkHostPath[256]{};
    char ThunkGuestPath[256]{};
    char ThunkConfigPath[256]{};

    int NumEnvironmentVariables{};
    int NumHostEnvironmentVariables{};
    int NumRootFSPaths = NamedRootFS.size();

    if (ImGui::BeginTabItem("Emulation")) {
      auto Value = LoadedConfig->Get(FEXCore::Config::ConfigOption::CONFIG_ROOTFS);
      if (Value.has_value() && !(*Value)->empty()) {
        strncpy(RootFS, &(*Value)->at(0), 256);
      }
      ImGui::Text("Available named RootFS folders: %d", NumRootFSPaths);

      if (ImGui::ListBox("Named RootFS folders", &NamedRootFSSelected, NamedRootFSVariableFiller, nullptr, NumRootFSPaths)) {
        strncpy(RootFS, NamedRootFS.at(NamedRootFSSelected).c_str(), 256);
        LoadedConfig->EraseSet(FEXCore::Config::ConfigOption::CONFIG_ROOTFS, RootFS);
        ConfigChanged = true;
      }

      if (ImGui::InputText("RootFS:", RootFS, 256, ImGuiInputTextFlags_EnterReturnsTrue)) {
        NamedRootFSSelected = -1;
        LoadedConfig->EraseSet(FEXCore::Config::ConfigOption::CONFIG_ROOTFS, RootFS);
        ConfigChanged = true;
      }

      Value = LoadedConfig->Get(FEXCore::Config::ConfigOption::CONFIG_THUNKHOSTLIBS);
      if (Value.has_value() && !(*Value)->empty()) {
        strncpy(ThunkHostPath, &(*Value)->at(0), 256);
      }
      if (ImGui::InputText("Thunk Host library folder:", ThunkHostPath, 256, ImGuiInputTextFlags_EnterReturnsTrue)) {
        LoadedConfig->EraseSet(FEXCore::Config::ConfigOption::CONFIG_THUNKHOSTLIBS, ThunkHostPath);
        ConfigChanged = true;
      }

      Value = LoadedConfig->Get(FEXCore::Config::ConfigOption::CONFIG_THUNKGUESTLIBS);
      if (Value.has_value() && !(*Value)->empty()) {
        strncpy(ThunkGuestPath, &(*Value)->at(0), 256);
      }
      if (ImGui::InputText("Thunk Guest library folder:", ThunkGuestPath, 256, ImGuiInputTextFlags_EnterReturnsTrue)) {
        LoadedConfig->EraseSet(FEXCore::Config::ConfigOption::CONFIG_THUNKGUESTLIBS, ThunkGuestPath);
        ConfigChanged = true;
      }

      Value = LoadedConfig->Get(FEXCore::Config::ConfigOption::CONFIG_THUNKCONFIG);
      if (Value.has_value() && !(*Value)->empty()) {
        strncpy(ThunkConfigPath, &(*Value)->at(0), 256);
      }
      if (ImGui::InputText("Thunk Config file:", ThunkConfigPath, 256, ImGuiInputTextFlags_EnterReturnsTrue)) {
        LoadedConfig->EraseSet(FEXCore::Config::ConfigOption::CONFIG_THUNKCONFIG, ThunkConfigPath);
        ConfigChanged = true;
      }

      auto ValueList = LoadedConfig->All(FEXCore::Config::ConfigOption::CONFIG_ENV);
      auto ValueHostList = LoadedConfig->All(FEXCore::Config::ConfigOption::CONFIG_HOSTENV);
      if (ValueList.has_value()) {
        NumEnvironmentVariables = (*ValueList)->size();
      }

      if (ValueHostList.has_value()) {
        NumHostEnvironmentVariables = (*ValueHostList)->size();
      }

      ImGui::Text("Number of environment variables: %d", NumEnvironmentVariables);

      ImGui::ListBox("Environment variables", &EnvironmentVariableSelected, EnvironmentVariableFiller<FEXCore::Config::ConfigOption::CONFIG_ENV>, nullptr, NumEnvironmentVariables);

      if (ImGui::SmallButton("+")) {
        ImGui::OpenPopup(EnvironmentPopupName);
      }

      if (NumEnvironmentVariables) {
        ImGui::SameLine();
        if (ImGui::SmallButton("-")) {
          DeleteEnvironmentVariable<FEXCore::Config::ConfigOption::CONFIG_ENV>(EnvironmentVariableSelected);
          EnvironmentVariableSelected = std::max(0, EnvironmentVariableSelected - 1);
        }
      }


      ImGui::PushID(1);
      ImGui::Text("Number of Host environment variables: %d", NumHostEnvironmentVariables);

      ImGui::ListBox("Host Env variables", &HostEnvironmentVariableSelected, EnvironmentVariableFiller<FEXCore::Config::ConfigOption::CONFIG_HOSTENV>, nullptr, NumHostEnvironmentVariables);

      if (ImGui::SmallButton("+")) {
        ImGui::OpenPopup(HostEnvironmentPopupName);
      }

      if (NumHostEnvironmentVariables) {
        ImGui::SameLine();
        if (ImGui::SmallButton("-")) {
          DeleteEnvironmentVariable<FEXCore::Config::ConfigOption::CONFIG_HOSTENV>(HostEnvironmentVariableSelected);
          HostEnvironmentVariableSelected = std::max(0, HostEnvironmentVariableSelected - 1);
        }
      }
      ImGui::PopID();

      // Only draws if popup is open
      AddNewEnvironmentVariable();

      ImGui::Text("Debugging:");
      Value = LoadedConfig->Get(FEXCore::Config::ConfigOption::CONFIG_O0);
      bool DisablePasses = Value.has_value() && **Value == "1";
      if (ImGui::Checkbox("Disable Optimization Passes", &DisablePasses)) {
        LoadedConfig->EraseSet(FEXCore::Config::ConfigOption::CONFIG_O0, DisablePasses ? "1" : "0");
        ConfigChanged = true;
      }

      ImGui::Text("Ahead Of Time JIT Options:");
      Value = LoadedConfig->Get(FEXCore::Config::ConfigOption::CONFIG_AOTIRGENERATE);
      bool AOTGenerate = Value.has_value() && **Value == "1";
      if (ImGui::Checkbox("Generate", &AOTGenerate)) {
        LoadedConfig->EraseSet(FEXCore::Config::ConfigOption::CONFIG_AOTIRGENERATE, AOTGenerate ? "1" : "0");
        ConfigChanged = true;
      }

      Value = LoadedConfig->Get(FEXCore::Config::ConfigOption::CONFIG_AOTIRCAPTURE);
      bool AOTCapture = Value.has_value() && **Value == "1";
      if (ImGui::Checkbox("Capture", &AOTCapture)) {
        LoadedConfig->EraseSet(FEXCore::Config::ConfigOption::CONFIG_AOTIRCAPTURE, AOTCapture ? "1" : "0");
        ConfigChanged = true;
      }

      Value = LoadedConfig->Get(FEXCore::Config::ConfigOption::CONFIG_AOTIRLOAD);
      bool AOTLoad = Value.has_value() && **Value == "1";
      if (ImGui::Checkbox("Load", &AOTLoad)) {
        LoadedConfig->EraseSet(FEXCore::Config::ConfigOption::CONFIG_AOTIRLOAD, AOTLoad ? "1" : "0");
        ConfigChanged = true;
      }

      Value = LoadedConfig->Get(FEXCore::Config::ConfigOption::CONFIG_CACHEOBJECTCODECOMPILATION);

      ImGui::Text("Cache JIT object code:");
      int CacheJITObjectCode = 0;

      Value = LoadedConfig->Get(FEXCore::Config::ConfigOption::CONFIG_CACHEOBJECTCODECOMPILATION);
      if (Value.has_value()) {
        if (**Value == "0") {
          CacheJITObjectCode = FEXCore::Config::ConfigObjectCodeHandler::CONFIG_NONE;
        } else if (**Value == "1") {
          CacheJITObjectCode = FEXCore::Config::ConfigObjectCodeHandler::CONFIG_READ;
        } else if (**Value == "2") {
          CacheJITObjectCode = FEXCore::Config::ConfigObjectCodeHandler::CONFIG_READWRITE;
        }
      }

      bool CacheChanged = false;
      CacheChanged |= ImGui::RadioButton("Off", &CacheJITObjectCode, FEXCore::Config::ConfigObjectCodeHandler::CONFIG_NONE); ImGui::SameLine();
      CacheChanged |= ImGui::RadioButton("Read-only", &CacheJITObjectCode, FEXCore::Config::ConfigObjectCodeHandler::CONFIG_READ); ImGui::SameLine();
      CacheChanged |= ImGui::RadioButton("Read/Write", &CacheJITObjectCode, FEXCore::Config::ConfigObjectCodeHandler::CONFIG_READWRITE);

      if (CacheChanged) {
        LoadedConfig->EraseSet(FEXCore::Config::ConfigOption::CONFIG_CACHEOBJECTCODECOMPILATION, std::to_string(CacheJITObjectCode));
        ConfigChanged = true;
      }

      ImGui::EndTabItem();
    }
  }

  void FillLoggingConfig() {
    char LogFile[256]{};
    char IRDump[256]{};

    if (ImGui::BeginTabItem("Logging")) {
      auto Value = LoadedConfig->Get(FEXCore::Config::ConfigOption::CONFIG_SILENTLOG);
      bool SilentLog = Value.has_value() && **Value == "1";
      if (ImGui::Checkbox("Silent Logging", &SilentLog)) {
        LoadedConfig->EraseSet(FEXCore::Config::ConfigOption::CONFIG_SILENTLOG, SilentLog ? "1" : "0");
        ConfigChanged = true;
      }

      Value = LoadedConfig->Get(FEXCore::Config::ConfigOption::CONFIG_OUTPUTLOG);
      if (Value.has_value() && !(*Value)->empty()) {
        strncpy(LogFile, &(*Value)->at(0), 256);
      }
      if (ImGui::InputText("Output log file:", LogFile, 256, ImGuiInputTextFlags_EnterReturnsTrue)) {
        LoadedConfig->EraseSet(FEXCore::Config::ConfigOption::CONFIG_OUTPUTLOG, LogFile);
        ConfigChanged = true;
      }

      Value = LoadedConfig->Get(FEXCore::Config::ConfigOption::CONFIG_DUMPIR);
      if (Value.has_value() && !(*Value)->empty()) {
        strncpy(IRDump, &(*Value)->at(0), 256);
      }
      if (ImGui::InputText("IR Dump location:", IRDump, 256, ImGuiInputTextFlags_EnterReturnsTrue)) {
        LoadedConfig->EraseSet(FEXCore::Config::ConfigOption::CONFIG_DUMPIR, IRDump);
        ConfigChanged = true;
      }

      ImGui::EndTabItem();
    }
  }

  void FillHackConfig() {
    if (ImGui::BeginTabItem("Hacks")) {
      auto Value = LoadedConfig->Get(FEXCore::Config::ConfigOption::CONFIG_TSOENABLED);
      bool TSOEnabled = Value.has_value() && **Value == "1";
      if (ImGui::Checkbox("TSO Enabled", &TSOEnabled)) {
        LoadedConfig->EraseSet(FEXCore::Config::ConfigOption::CONFIG_TSOENABLED, TSOEnabled ? "1" : "0");
        ConfigChanged = true;
      }

      Value = LoadedConfig->Get(FEXCore::Config::ConfigOption::CONFIG_PARANOIDTSO);
      bool ParanoidTSOEnabled = Value.has_value() && **Value == "1";
      if (ImGui::Checkbox("Paranoid TSO Enabled", &ParanoidTSOEnabled)) {
        LoadedConfig->EraseSet(FEXCore::Config::ConfigOption::CONFIG_PARANOIDTSO, ParanoidTSOEnabled ? "1" : "0");
        ConfigChanged = true;
      }

      Value = LoadedConfig->Get(FEXCore::Config::ConfigOption::CONFIG_X87REDUCEDPRECISION);
      bool X87ReducedPrecision = Value.has_value() && **Value == "1";
      if (ImGui::Checkbox("X87 Reduced Precision", &X87ReducedPrecision)) {
        LoadedConfig->EraseSet(FEXCore::Config::ConfigOption::CONFIG_X87REDUCEDPRECISION, X87ReducedPrecision ? "1" : "0");
        ConfigChanged = true;
      }

      ImGui::Text("SMC Checks: ");
      int SMCChecks = FEXCore::Config::CONFIG_SMC_MMAN;

      Value = LoadedConfig->Get(FEXCore::Config::ConfigOption::CONFIG_SMCCHECKS);
      if (Value.has_value()) {
        if (**Value == "0") {
          SMCChecks = FEXCore::Config::CONFIG_SMC_NONE;
        } else if (**Value == "1") {
          SMCChecks = FEXCore::Config::CONFIG_SMC_MTRACK;
        } else if (**Value == "2") {
          SMCChecks = FEXCore::Config::CONFIG_SMC_FULL;
        } else if (**Value == "3") {
          SMCChecks = FEXCore::Config::CONFIG_SMC_MMAN;
        }
      }

      bool SMCChanged = false;
      SMCChanged |= ImGui::RadioButton("None", &SMCChecks, FEXCore::Config::CONFIG_SMC_NONE); ImGui::SameLine();
      SMCChanged |= ImGui::RadioButton("MTrack (Default)", &SMCChecks, FEXCore::Config::CONFIG_SMC_MTRACK); ImGui::SameLine();
      SMCChanged |= ImGui::RadioButton("Full", &SMCChecks, FEXCore::Config::CONFIG_SMC_FULL);
      SMCChanged |= ImGui::RadioButton("MMan (Deprecated)", &SMCChecks, FEXCore::Config::CONFIG_SMC_MMAN); ImGui::SameLine();

      if (SMCChanged) {
        LoadedConfig->EraseSet(FEXCore::Config::ConfigOption::CONFIG_SMCCHECKS, std::to_string(SMCChecks));
      }

      Value = LoadedConfig->Get(FEXCore::Config::ConfigOption::CONFIG_ABILOCALFLAGS);
      bool UnsafeLocalFlags = Value.has_value() && **Value == "1";
      if (ImGui::Checkbox("Unsafe local flags optimization", &UnsafeLocalFlags)) {
        LoadedConfig->EraseSet(FEXCore::Config::ConfigOption::CONFIG_ABILOCALFLAGS, UnsafeLocalFlags ? "1" : "0");
        ConfigChanged = true;
      }

      Value = LoadedConfig->Get(FEXCore::Config::ConfigOption::CONFIG_ABINOPF);
      bool NoPFCalculation = Value.has_value() && **Value == "1";
      if (ImGui::Checkbox("Disable PF calculation", &NoPFCalculation)) {
        LoadedConfig->EraseSet(FEXCore::Config::ConfigOption::CONFIG_ABINOPF, NoPFCalculation ? "1" : "0");
        ConfigChanged = true;
      }

      ImGui::EndTabItem();
    }
  }

  static const std::map<FEXCore::Config::ConfigOption, std::string> ConfigToNameLookup = {{
#define OPT_BASE(type, group, enum, json, default) {FEXCore::Config::ConfigOption::CONFIG_##enum, #json},
#include <FEXCore/Config/ConfigValues.inl>
  }};

  struct TmpString {
    char Str[256];
    std::string Name;
  };
  static std::vector<std::vector<TmpString>> AdvancedOptions{};

  void UpdateAdvancedOptionsVector() {
    AdvancedOptions.clear();
    // Push everything in to our vector table that we can modify instead of the map
    auto Options = LoadedConfig->GetOptionMap();
    AdvancedOptions.resize(Options.size());

    size_t i = 0;
    for (auto &Option : Options) {
      auto ConfigName = ConfigToNameLookup.find(Option.first);
      auto &AdvancedOption  = AdvancedOptions.at(i);
      AdvancedOption.resize(Option.second.size());
      size_t j = 0;
      for (auto &OptionList : Option.second) {
        strcpy(AdvancedOption[j].Str, OptionList.c_str());
        AdvancedOption[j].Name = ConfigName->second;
        if (Option.second.size() > 1) {
          AdvancedOption[j].Name += " " + std::to_string(j);
        }
        ++j;
      }
      ++i;
    }
  }

  void FillAdvancedConfig() {
    if (ImGui::BeginTabItem("Advanced")) {
      auto Options = LoadedConfig->GetOptionMap();

      if (ImGui::SmallButton("Refresh") || AdvancedOptions.size() != Options.size()) {
        UpdateAdvancedOptionsVector();
      }

      if (Options.size()) {
        ImGui::Columns(2);
        size_t i = 0;
        for (auto &Option : Options) {
          auto ConfigName = ConfigToNameLookup.find(Option.first);
          ImGui::Text("%s", ConfigName->second.c_str());
          ImGui::NextColumn();

          auto &AdvancedOption = AdvancedOptions.at(i);
          size_t j = 0;

          bool Stop = false;
          for (auto &OptionList : AdvancedOption) {
            //ImGui::Text("%s", OptionList.Str);
            if (ImGui::InputText(OptionList.Name.c_str(), OptionList.Str, sizeof(TmpString), ImGuiInputTextFlags_EnterReturnsTrue)) {
              if (Option.second.size() == 1) {
                LoadedConfig->EraseSet(Option.first, OptionList.Str);
              }
              else {
                auto &All = Option.second;
                auto Iter = All.begin();
                std::advance(Iter, j);
                *Iter = OptionList.Str;

                LoadedConfig->Erase(Option.first);
                for (auto &Value : All) {
                  LoadedConfig->Set(Option.first, Value);
                }

              }
              ConfigChanged = true;
            }

            ImGui::SameLine();

            ImGui::PushID(OptionList.Name.c_str());
            if (ImGui::SmallButton("-")) {
              if (Option.second.size() == 1) {
                LoadedConfig->Erase(Option.first);
              }
              else {
                auto &All = Option.second;
                auto Iter = All.begin();
                std::advance(Iter, j);
                All.erase(Iter);

                LoadedConfig->Erase(Option.first);
                for (auto &Value : All) {
                  LoadedConfig->Set(Option.first, Value);
                }
              }
              Stop = true;
              ConfigChanged = true;
              UpdateAdvancedOptionsVector();
            }
            ImGui::PopID();

            ++j;
          }

          ImGui::NextColumn();
          ++i;
          if (Stop) {
            break;
          }
        }
      }
      ImGui::EndTabItem();
    }
  }

  void FillConfigWindow() {
    ImGui::BeginTabBar("Config");
    FillCPUConfig();
    FillEmulationConfig();
    FillLoggingConfig();
    FillHackConfig();
    FillAdvancedConfig();
    ImGui::EndTabBar();
  }

  bool DrawUI() {
    ImGuiIO& io = ImGui::GetIO();
    auto current_time = std::chrono::high_resolution_clock::now();
    auto Diff = std::chrono::duration_cast<std::chrono::milliseconds>(current_time - GlobalTime);
    io.DeltaTime = Diff.count() > 0 ? Diff.count() : 1.0f/60.0f;
    GlobalTime = current_time;

    ImGui::NewFrame();

    // We are using the ImGuiWindowFlags_NoDocking flag to make the parent window not dockable into,
    // because it would be confusing to have two docking targets within each others.
    ImGuiWindowFlags window_flags = ImGuiWindowFlags_MenuBar | ImGuiWindowFlags_NoDocking;
    ImGuiViewport* viewport = ImGui::GetMainViewport();
    ImGui::SetNextWindowPos(viewport->Pos);
    ImGui::SetNextWindowSize(viewport->Size);
    ImGui::SetNextWindowViewport(viewport->ID);
    ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);
    ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f);
    window_flags |= ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove;
    window_flags |= ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoNavFocus;

    ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f));
    ImGui::Begin("DockSpace", nullptr, window_flags);
    ImGui::PopStyleVar(3);

    struct {
      bool Open{};
      bool OpenDefault{};
      bool OpenAppProfile{};
      bool Save{};
      bool SaveAs{};
      bool SaveDefault{};
      bool Close{};
      bool Quit{};
    } Selected;

    char AppName[256]{};

    if (ImGui::BeginMenuBar()) {
      if (ImGui::BeginMenu("File")) {
        ImGui::MenuItem("Open", "CTRL+O", &Selected.Open, true);
        ImGui::MenuItem("Open from default location", "CTRL+SHIFT+O", &Selected.OpenDefault, true);
        ImGui::MenuItem("Open App profile", "CTRL+I", &Selected.OpenAppProfile, true);

        ImGui::MenuItem("Save", "CTRL+S", &Selected.Save, true);
        ImGui::MenuItem("Save As", "CTRL+SHIFT+S", &Selected.SaveAs, true);
        ImGui::MenuItem("Save As App profile", "CTRL+E", nullptr, true);
        ImGui::MenuItem("Save Default", "CTRL+SHIFT+P", &Selected.SaveDefault, true);

        ImGui::MenuItem("Close", "CTRL+W", &Selected.Close, true);
        ImGui::MenuItem("Quit", "CTRL+Q", &Selected.Quit, true);

        ImGui::EndMenu();
      }

      ImGui::SameLine(ImGui::GetWindowWidth() - ImGui::GetFrameHeight());

      if (ConfigOpen) {
        if (ConfigChanged) {
          ImGui::PushStyleColor(ImGuiCol_FrameBg,   ImVec4(1.0, 1.0, 0.0, 1.0));
          ImGui::PushStyleColor(ImGuiCol_CheckMark, ImVec4(1.0, 1.0, 0.0, 1.0));
        }
        else {
          ImGui::PushStyleColor(ImGuiCol_FrameBg,   ImVec4(0.0, 1.0, 0.0, 1.0));
          ImGui::PushStyleColor(ImGuiCol_CheckMark, ImVec4(0.0, 1.0, 0.0, 1.0));
        }
      }
      else {
        ImGui::PushStyleColor(ImGuiCol_FrameBg,   ImVec4(1.0, 0.0, 0.0, 1.0));
        ImGui::PushStyleColor(ImGuiCol_CheckMark, ImVec4(1.0, 0.0, 0.0, 1.0));
      }

      ImGui::RadioButton("", true);
      ImGui::PopStyleColor(2);

      ImGui::EndMenuBar();
    }

    if (ConfigOpen) {
      if (ImGui::BeginChild("#Config")) {
        FillConfigWindow();
      }

      if (ImGui::IsKeyPressed(SDL_SCANCODE_E) && io.KeyCtrl && !io.KeyShift) {
        ImGui::OpenPopup(SavedPopupAppName);
      }

      ImGui::SetNextWindowPos(ImVec2(viewport->Pos.x + viewport->Size.x / 2, viewport->Pos.y + viewport->Size.y / 2), ImGuiCond_Appearing, ImVec2(0.5f, 0.5f));
      if (ImGui::BeginPopupModal(SavedPopupAppName)) {
        ImGui::SetKeyboardFocusHere();
        if (ImGui::InputText("App name", AppName, 256, ImGuiInputTextFlags_EnterReturnsTrue)) {
          std::string AppNameString = AppName;
          std::string Filename = FEXCore::Config::GetApplicationConfig(AppNameString, false);
          SaveFile(Filename);
          ImGui::CloseCurrentPopup();
        }

        if (ImGui::IsKeyPressed(SDL_SCANCODE_ESCAPE)) {
          ImGui::CloseCurrentPopup();
        }
        ImGui::EndPopup();
      }

      ImGui::EndChild();
    }

    // Need this frame delay loop since ImGui doesn't allow us to enable a popup near the end of the frame
    if (OpenMsgPopup) {
      ImGui::OpenPopup(MsgPopupName);
      OpenMsgPopup = false;
    }

    if (Selected.OpenAppProfile ||
        (ImGui::IsKeyPressed(SDL_SCANCODE_I) && io.KeyCtrl && !io.KeyShift)) {
      ImGui::OpenPopup(OpenedPopupAppName);
    }

    // Center the saved popup in the center of the window
    ImGui::SetNextWindowPos(ImVec2(viewport->Pos.x + viewport->Size.x / 2, viewport->Pos.y + viewport->Size.y / 2), ImGuiCond_Appearing, ImVec2(0.5f, 0.5f));

    if (ImGui::BeginPopup(MsgPopupName)) {
      ImGui::Text("%s", MsgMessage.c_str());
      if ((std::chrono::high_resolution_clock::now() - MsgTimerStart) >= std::chrono::seconds(2)) {
        ImGui::CloseCurrentPopup();
      }
      ImGui::EndPopup();
    }
    else if (SaveMsgIsOpen) {
      SaveMsgIsOpen = false;
    }

    ImGui::SetNextWindowPos(ImVec2(viewport->Pos.x + viewport->Size.x / 2, viewport->Pos.y + viewport->Size.y / 2), ImGuiCond_Appearing, ImVec2(0.5f, 0.5f));
    if (ImGui::BeginPopupModal(OpenedPopupAppName)) {

      ImGui::SetKeyboardFocusHere();
      if (ImGui::InputText("App name", AppName, 256, ImGuiInputTextFlags_EnterReturnsTrue)) {
        std::string AppNameString = AppName;
        std::string Filename = FEXCore::Config::GetApplicationConfig(AppNameString, false);
        OpenFile(Filename, false);
        ImGui::CloseCurrentPopup();
      }

      if (ImGui::IsKeyPressed(SDL_SCANCODE_ESCAPE)) {
        ImGui::CloseCurrentPopup();
      }
      ImGui::EndPopup();
    }

    if (Selected.Open ||
        (ImGui::IsKeyPressed(SDL_SCANCODE_O) && io.KeyCtrl && !io.KeyShift)) {
      SelectedOpenFile = true;
    }
    if (Selected.OpenDefault ||
        (ImGui::IsKeyPressed(SDL_SCANCODE_O) && io.KeyCtrl && io.KeyShift)) {
      if (OpenFile(FEXCore::Config::GetConfigFileLocation(), true)) {
        LoadNamedRootFSFolder();
        SetupINotify();
      }
    }

    if (Selected.Save ||
        (ImGui::IsKeyPressed(SDL_SCANCODE_S) && io.KeyCtrl && !io.KeyShift)) {
      SaveFile(ConfigFilename);
    }
    if (Selected.SaveAs ||
        (ImGui::IsKeyPressed(SDL_SCANCODE_S) && io.KeyCtrl && io.KeyShift)) {
      SelectedSaveFileAs = true;
    }

    if (Selected.SaveDefault ||
        (ImGui::IsKeyPressed(SDL_SCANCODE_P) && io.KeyCtrl && io.KeyShift)) {
      SaveFile(FEXCore::Config::GetConfigFileLocation());
    }
    if (Selected.Close ||
        (ImGui::IsKeyPressed(SDL_SCANCODE_W) && io.KeyCtrl && !io.KeyShift)) {
      CloseConfig();
      ShutdownINotify();
    }

    if (Selected.Quit ||
        (ImGui::IsKeyPressed(SDL_SCANCODE_Q) && io.KeyCtrl && !io.KeyShift)) {
      Selected.Quit = true;
    }

    ImGui::End(); // End dockspace

    char const *InitialPath;
    char const *File;

    InitialPath = DialogOpen.chooseFileDialog(SelectedOpenFile, "./", ".json", "#Chose a config to load");
    File = DialogOpen.getChosenPath();
    if (strlen(InitialPath) > 0 && strlen(File) > 0) {
      OpenFile(File);
    }

    InitialPath = DialogSaveAs.saveFileDialog(SelectedSaveFileAs, "./", "Config.json", ".json", "#Choose where to save a config");
    File = DialogSaveAs.getChosenPath();
    if (strlen(InitialPath) > 0 && strlen(File) > 0) {
      SaveFile(File);
    }

    SelectedOpenFile = false;
    SelectedSaveFileAs = false;

    ImGui::Render();

    // Return true to keep rendering
    return !Selected.Quit;
  }
}

int main(int argc, char **argv) {
  std::string ImGUIConfig = FEXCore::Config::GetConfigDirectory(false) + "FEXConfig_imgui.ini";
  auto [window, gl_context] = FEX::GUI::SetupIMGui("#FEXConfig", ImGUIConfig);

  GlobalTime = std::chrono::high_resolution_clock::now();

  // Attempt to open the config passed in
  if (argc > 1) {
    if (OpenFile(argv[1], true)) {
      LoadNamedRootFSFolder();
      SetupINotify();
    }
  }
  else {
    if (OpenFile(FEXCore::Config::GetConfigFileLocation(), true)) {
      LoadNamedRootFSFolder();
      SetupINotify();
    }
  }

  FEX::GUI::DrawUI(window, DrawUI);

  ShutdownINotify();

  // Cleanup
  FEX::GUI::Shutdown(window, gl_context);
  return 0;
}
